package com.aphrodite.ui.widgets.barcode;

import java.util.Hashtable;
import java.util.Vector;

import com.aphrodite.util.geom.Dimension;

/**
 * European Article number 13 barcode widget.
 * 
 * 
 * @see UPCABarcode
 * 
 */
class EAN13Barcode extends UPCABarcode {
	private static final class ModuleTemplate extends UPCABarcode.ModuleTemplate {

		protected static final int EVEN = 1;
		public static final int GUARD_CHAR_SIZE = 0;

		protected static final Vector KEYS_LEFT_EVEN = new Vector();
		protected static final Vector KEYS_LEFT_ODD = new Vector();

		public static final int LEFT_WIDTH = 7;

		protected static final int ODD = 0;

		protected static final Hashtable PARITY_TABLE = new Hashtable();
		protected static final Module RIGHT_MARGIN = new BlankModule(7);

		protected static final Hashtable SET_LEFT_EVEN = new Hashtable();
		protected static final Hashtable SET_LEFT_ODD = new Hashtable();

		static {
			initBaseSet();
		}

		/**
		 * Returns the module that represents the specified character.
		 * 
		 * @param key
		 *            The data character to get the encoding module for
		 * @param position
		 *            The position of the data character, starts at 0
		 * @return The module that encodes the given char
		 */
		static Module getModule(String firstChar, String key, int position) {
			Module module = null;

			/*
			 * with the human readble, the left side has 7 chars, but the
			 * encoding only has 6. this is due to the fact that the first char
			 * of the left side is not encoded.
			 */

			if (position + 1 > LEFT_WIDTH) {
				module = (Module) SET_RIGHT.get(key);
			} else {
				int[] parityRef = (int[]) PARITY_TABLE.get(firstChar);

				if (parityRef[position - 1] == ODD) {
					module = (Module) SET_LEFT_ODD.get(key);
				} else {
					module = (Module) SET_LEFT_EVEN.get(key);
				}
			}

			module.setSymbol(key);
			return module;
		}

		/**
		 * Returns the encoded module at the given index position. This is used
		 * to get the encoded checksum character.
		 * 
		 * @param index
		 *            The index of the module required
		 * @return The module at the specified index
		 */
		static Module getModuleForIndex(String firstChar, int index) {
			if (index + 1 > LEFT_WIDTH) {
				return getModule((String) KEYS_RIGHT.elementAt(index), index);
			} else {
				int[] parityRef = (int[]) PARITY_TABLE.get(firstChar);

				if (parityRef[index - 1] == ODD) {
					return getModule((String) KEYS_LEFT_ODD.elementAt(index), index);
				} else {
					return getModule((String) KEYS_LEFT_EVEN.elementAt(index), index);
				}
			}
		}

		/**
		 * Indicates whether the given key is represented in the default
		 * encoding table that this module factory contains.
		 * 
		 * @return True if the key has a direct module encoding, false if not
		 */
		static boolean hasModule(String key) {
			if (KEYS_RIGHT.indexOf(key) > -1) {
				return true;
			}

			if (KEYS_LEFT_ODD.indexOf(key) > -1) {
				return true;
			}

			if (KEYS_LEFT_EVEN.indexOf(key) > -1) {
				return true;
			}

			return false;
		}

		/**
		 * Initialise the module definitions.
		 */
		protected static void initBaseSet() {
			initRightSet();

			initLeftOddSet();
			initLeftEvenSet();

			/*
			 * map - First number system digit 0 - second number system digit 1
			 * -> 5 - manufacturer code characters
			 */
			PARITY_TABLE.put("0", new int[] { ODD, ODD, ODD, ODD, ODD, ODD });
			PARITY_TABLE.put("1", new int[] { ODD, ODD, EVEN, ODD, EVEN, EVEN });
			PARITY_TABLE.put("2", new int[] { ODD, ODD, EVEN, EVEN, ODD, EVEN });
			PARITY_TABLE.put("3", new int[] { ODD, ODD, EVEN, EVEN, EVEN, ODD });
			PARITY_TABLE.put("4", new int[] { ODD, EVEN, ODD, ODD, EVEN, EVEN });
			PARITY_TABLE.put("5", new int[] { ODD, EVEN, EVEN, ODD, ODD, EVEN });
			PARITY_TABLE.put("6", new int[] { ODD, EVEN, EVEN, EVEN, ODD, ODD });
			PARITY_TABLE.put("7", new int[] { ODD, EVEN, ODD, EVEN, ODD, EVEN });
			PARITY_TABLE.put("8", new int[] { ODD, EVEN, ODD, EVEN, EVEN, ODD });
			PARITY_TABLE.put("9", new int[] { ODD, EVEN, EVEN, ODD, EVEN, ODD });
		}

		protected static void initLeftEvenSet() {
			// left side odd parity
			KEYS_LEFT_EVEN.addElement("0");
			SET_LEFT_EVEN.put("0", new Module(new int[] { 0, 1, 1, 2, 3 })); // 0100111
			KEYS_LEFT_EVEN.addElement("1");
			SET_LEFT_EVEN.put("1", new Module(new int[] { 0, 1, 2, 2, 2 })); // 0110011
			KEYS_LEFT_EVEN.addElement("2");
			SET_LEFT_EVEN.put("2", new Module(new int[] { 0, 2, 2, 1, 2 })); // 0011011
			KEYS_LEFT_EVEN.addElement("3");
			SET_LEFT_EVEN.put("3", new Module(new int[] { 0, 1, 1, 4, 1 })); // 0100001
			KEYS_LEFT_EVEN.addElement("4");
			SET_LEFT_EVEN.put("4", new Module(new int[] { 0, 2, 3, 1, 1 })); // 0011101
			KEYS_LEFT_EVEN.addElement("5");
			SET_LEFT_EVEN.put("5", new Module(new int[] { 0, 1, 3, 2, 1 })); // 0111001
			KEYS_LEFT_EVEN.addElement("6");
			SET_LEFT_EVEN.put("6", new Module(new int[] { 0, 4, 1, 1, 1 })); // 0000101
			KEYS_LEFT_EVEN.addElement("7");
			SET_LEFT_EVEN.put("7", new Module(new int[] { 0, 2, 1, 3, 1 })); // 0010001
			KEYS_LEFT_EVEN.addElement("8");
			SET_LEFT_EVEN.put("8", new Module(new int[] { 0, 3, 1, 2, 1 })); // 0001001
			KEYS_LEFT_EVEN.addElement("9");
			SET_LEFT_EVEN.put("9", new Module(new int[] { 0, 2, 1, 1, 3 })); // 0010111
		}

		protected static void initLeftOddSet() {
			// left side even parity
			KEYS_LEFT_ODD.addElement("0");
			SET_LEFT_ODD.put("0", new Module(new int[] { 0, 3, 2, 1, 1 })); // 0001101
			KEYS_LEFT_ODD.addElement("1");
			SET_LEFT_ODD.put("1", new Module(new int[] { 0, 2, 2, 2, 1 })); // 0011001
			KEYS_LEFT_ODD.addElement("2");
			SET_LEFT_ODD.put("2", new Module(new int[] { 0, 2, 1, 2, 2 })); // 0010011
			KEYS_LEFT_ODD.addElement("3");
			SET_LEFT_ODD.put("3", new Module(new int[] { 0, 1, 4, 1, 1 })); // 0111101
			KEYS_LEFT_ODD.addElement("4");
			SET_LEFT_ODD.put("4", new Module(new int[] { 0, 1, 1, 3, 2 })); // 0100011
			KEYS_LEFT_ODD.addElement("5");
			SET_LEFT_ODD.put("5", new Module(new int[] { 0, 1, 2, 3, 1 })); // 0110001
			KEYS_LEFT_ODD.addElement("6");
			SET_LEFT_ODD.put("6", new Module(new int[] { 0, 1, 1, 1, 4 })); // 0101111
			KEYS_LEFT_ODD.addElement("7");
			SET_LEFT_ODD.put("7", new Module(new int[] { 0, 1, 3, 1, 2 })); // 0111011
			KEYS_LEFT_ODD.addElement("8");
			SET_LEFT_ODD.put("8", new Module(new int[] { 0, 1, 2, 1, 3 })); // 0110111
			KEYS_LEFT_ODD.addElement("9");
			SET_LEFT_ODD.put("9", new Module(new int[] { 0, 3, 1, 1, 2 })); // 0001011
		}

		/**
		 * Indicates whether the given character is valid for this barcode or
		 * not. This basically just checks to see whether the key is in the list
		 * of encoded characters.
		 * 
		 * @param key
		 *            The key to check for validity
		 * @return True if the key is valid, false otherwise
		 */
		static boolean isValid(String key) {
			if (KEYS_RIGHT.indexOf(key) > -1) {
				return true;
			}

			if (KEYS_LEFT_ODD.indexOf(key) > -1) {
				return true;
			}

			if (KEYS_LEFT_EVEN.indexOf(key) > -1) {
				return true;
			}

			return false;
		}

		/**
		 * Cannot construct.
		 */
		protected ModuleTemplate() {
			super();
		}
	}

	final static int BARCODE_LENGTH = 12;
	final static String ISBN_NUMBER_SYSTEM = "978";
	final static int ISBN_SIZE = 10;

	final static int LEFT_EVEN = 1;
	final static int LEFT_ODD = 0;

	final static int PARITY_EVEN = 0;

	final static int PARITY_ODD = 1;
	final static int RIGHT = 2;

	/**
	 * A list of type identifiers for the EAN13 barcode format
	 */
	public static final String[] TYPES = new String[] { "EAN-13", "EAN13" };

	/**
	 * Constructs a basic mode EAN13 barcode with the specified data and an
	 * optional checksum. The length of the barcode is 11, 12 with a checksum.
	 * If the length passed is only 11, then a checksum will be automaticaly
	 * added. If the length is not 11 or 12 then a barcode exception will be
	 * thrown.
	 * 
	 * @param data
	 *            The data to encode
	 * @param height
	 * @throws BarcodeException
	 *             If the data to be encoded is invalid
	 */
	EAN13Barcode(String data, int height) throws BarcodeException {
		super(data, false, height);
	}

	/**
	 * Returns the checksum for the barcode, pre-encoded as a Module.
	 * 
	 * @return a Mod-10 caclulated checksum, if no checksum is required Null
	 */
	protected Module calculateChecksum() {
		if (requiresChecksum) {
			return ModuleTemplate.getModuleForIndex(data.substring(0, 1), getMod10CheckDigit(data));
		}

		return null;
	}

	/*
	 * TODO: The following is very close to the parent (UPCABarcode), should
	 * change parent so can handle both.
	 * 
	 * note that the following code uses member functions to get static values
	 * from the ModuleFactory class, instead of referencing the values directly.
	 * This is so sub-classes can override the member functions and thus change
	 * the static values.
	 */
	protected Dimension draw(Output output, int x, int y, int barWidth, int barHeight) throws OutputException {
		int currentX = x;

		output.beginDraw();

		// need to change the output.barHeight value, appears to be no means to
		// do so
		int guardBarHeight = 0;
		int shortBarHeight = barHeight;
		int textHeight = 10 * barWidth;

		if (drawingText) {
			shortBarHeight = barHeight - (11 * barWidth);
			guardBarHeight = shortBarHeight + (6 * barWidth);
		} else {
			shortBarHeight = barHeight - (6 * barWidth);
			guardBarHeight = barHeight;
		}

		String text = getLabel();
		int currentY = super.height + y;

		Module[] modules = encodeData();

		String leadChar = String.valueOf(text.charAt(0));
		String firstSet = text.substring(1, getLeftWidth());
		String lastSet = text.substring(getLeftWidth());

		if (requiresChecksum) {
			lastSet = lastSet + calculateChecksum().getSymbol();
		}

		int startTextPos = 0;
		int firstTextPos = 0;
		int secondTextPos = 0;

		int startTextW = x;
		int firstTextW = 0;
		int secondTextW = 0;
		int width = 0;
		Module preAmble = getPreAmble();
		Module postAmble = getPostAmble();
		startTextW = 0;

		// draw leading white space
		if (super.drawingQuietSection) {
			currentX += drawModule(getLeftMargin(), output, currentX, y, barWidth, shortBarHeight + textHeight);
		}
		startTextPos = x;
		startTextW = currentX - startTextPos;
		width = currentX;
		int guardCharSize = getGuardCharSize();
		int leftWidth = getLeftWidth() - 1;

		// draw the left guard
		if (preAmble != null) {
			currentX += drawModule(preAmble, output, currentX, y, barWidth, guardBarHeight);
		}

		// draw first char in left side
		for (int i = 0; i < guardCharSize; i++) {
			currentX += drawModule(modules[0], output, currentX, y, barWidth, guardBarHeight);
		}
		firstTextPos = currentX;

		// draw the blank space below the guard
		width = currentX - width;
		output.paintBackground(currentX - width, guardBarHeight + currentY, width,
				((shortBarHeight + textHeight) - guardBarHeight));

		for (int i = guardCharSize; i < leftWidth; i++) {
			currentX += drawModule(modules[i], output, currentX, y, barWidth, shortBarHeight);
		}

		firstTextW = currentX - firstTextPos;

		width = currentX;
		// draw the centre guard
		currentX += drawModule(getCentreGuard(), output, currentX, y, barWidth, guardBarHeight);
		secondTextPos = currentX;

		// draw the blank space below the guard
		width = currentX - width;
		output.paintBackground(currentX - width, guardBarHeight + currentY, width,
				((shortBarHeight + textHeight) - guardBarHeight));

		for (int i = leftWidth; i < modules.length; i++) {
			currentX += drawModule(modules[i], output, currentX, y, barWidth, shortBarHeight);
		}

		secondTextW = currentX - secondTextPos;
		width = currentX;

		// draw the right guard
		if (postAmble != null) {
			currentX += drawModule(postAmble, output, currentX, y, barWidth, guardBarHeight);
		}

		// draw the blank space below the guard
		width = currentX - width;
		output.paintBackground(currentX - width, guardBarHeight + currentY, width,
				((shortBarHeight + textHeight) - guardBarHeight));

		// draw trailing white space
		currentX += drawModule(getRightMargin(), output, currentX, y, barWidth, shortBarHeight + textHeight);

		// Removed text rendering
		// if (drawingText) {
		// output.drawText(leadChar,
		// LabelLayoutFactory.createMarginLayout(startTextPos, shortBarHeight,
		// startTextW,
		// textHeight));
		// output.drawText(firstSet,
		// LabelLayoutFactory.createMarginLayout(firstTextPos, shortBarHeight,
		// firstTextW,
		// textHeight));
		// output.drawText(lastSet,
		// LabelLayoutFactory.createMarginLayout(secondTextPos, shortBarHeight,
		// secondTextW,
		// textHeight));
		// }

		Dimension size = new Dimension((currentX - x), (currentY) - y);

		output.endDraw((int) size.getWidth(), (int) size.getHeight());

		return size;
	}

	/**
	 * Returns the encoded data for the barcode.
	 * 
	 * @return An array of modules that represent the data as a barcode
	 */
	protected Module[] encodeData() {
		Vector modules = new Vector();
		Module module = null;
		int len = data.length();
		char c;

		String firstChar = data.substring(0, 1);

		/*
		 * EAN13 is a funny one. The first digit actualy isn't encoded, even
		 * though its displayed on the human readable.
		 * 
		 * go figure.
		 */
		for (int i = 1; i < len; i++) {
			c = data.charAt(i);

			module = ModuleTemplate.getModule(firstChar, String.valueOf(c), i);
			width += module.widthInBars();
			modules.addElement(module);
		}

		if (requiresChecksum) {
			module = ModuleTemplate.getModule(firstChar, calculateChecksum().getSymbol(), modules.size() - 1);
			width += module.widthInBars();
			modules.addElement(module);
		}
		Module[] ret = new Module[modules.size()];
		modules.copyInto(ret);
		return ret;
	}

	protected int getBarcodeLength() {
		return BARCODE_LENGTH;
	}

	protected int getGuardCharSize() {
		return ModuleTemplate.GUARD_CHAR_SIZE;
	}

	protected int getLeftWidth() {
		return ModuleTemplate.LEFT_WIDTH;
	}

	protected int getMod10CheckDigit(String data) {
		int sum = 0;
		int len = data.length();
		int value;

		/*
		 * note that the for loop is from 0, as indexing for data is from 0, but
		 * the modolo 10 considers the first character position to be 1. in UPCA
		 * 0 is odd, as the first position (being 1) is considered an odd
		 * number. However EAN13 is one extra character in size. To be backward
		 * compatible EAN13 has a '0' added to the front for UPCA numbers and as
		 * such the first position is now even. So the following code must
		 * compare against 0, not 1 as is in the UPCA getMod10CheckDigit code.
		 */
		for (int i = 0; i < len; i++) {
			try {
				value = Integer.parseInt(String.valueOf(data.charAt(i)));
				sum += calculateChecksum(value, (i % 2) == 0);
			} catch (java.lang.NumberFormatException e) {
			}
		}

		return 10 - (sum % 10);
	}

	protected Module getRightMargin() {
		return ModuleTemplate.RIGHT_MARGIN;
	}

}
